//
// @Author: Zhenwei Xiao
// @Description:
// @Version: 1.0.0
// @Date: 2021/3/29 上午10:14
// @Copyright: MIN-Group；国家重大科技基础设施——未来网络北大实验室；深圳市信息论与未来网络重点实验室
//
const interes = require("../packet/Interest")
const logicface = require("./Logicface")
const timeouteventheap = require("./TimeoutEvent")
const data = require("../packet/Data")
const nack = require("../packet/Nack")
const tlv = require("../encoding/TLV")
const timeoutevent = require("./TimeoutEvent")

//
// @Description:  PIT表项，用于记录每个已发出的兴趣包
//
class PITEntry {
    constructor() {
        this.interest   = new interes.Interest  // 兴趣包
        this.expireTime = BigInt(0)       // 收到对应的数据包的处理函数
    }
    // 收到对应的数据包的处理函数
    onData(interest, data){

    }
    // 兴趣包超时的处理函数
    onTimeout(interest){

    }
    // 兴趣包无路由的处理函数
    onNack(interest, nack){

    }
    // interest   *packet.Interest                     // 兴趣包
    // onData     func(*packet.Interest, *packet.Data) // 收到对应的数据包的处理函数
    // onTimeout  func(*packet.Interest)               // 兴趣包超时的处理函数
    // onNack     func(*packet.Interest, *packet.Nack) // 兴趣包无路由的处理函数
    // expireTime int64                                // 本PIT表项的超时时间
}

class PendingPacket {
    constructor() {
        this.interest   = new interes.Interest
        this.data       = new data.Data
    }
    // 收到对应的数据包的处理函数
    onData(interest, data){

    }
    // 兴趣包超时的处理函数
    onTimeout(interest){

    }
    // 兴趣包无路由的处理函数
    onNack(interest, nack){

    }
    // onData    func(*packet.Interest, *packet.Data)
    // onTimeout func(*packet.Interest)
    // onNack    func(*packet.Interest, *packet.Nack)
}

//
// @Description:  内容中心网络类型的 LogicFace 类
//
class LogicFaceICN extends logicface.LogicFace{
    constructor(m) {
        super(m)
        // Q：定时器和锁？
        // this.LogicFace = new logicface.LogicFace()
        this.timer = null
        this.recentExpireTime = BigInt(0)
        this.mPit = new Map
        this.timeoutEventHeapLock = null
        this.timeoutEventHeap = new timeouteventheap.TimeoutEventHeap()
        this.mFib = new Map
        this.pendingPacketList = new Array()
    }
    // LogicFace
    // timer                *time.Timer          // 定时器，在processEvent函数中就被设置成很大的值
    // recentExpireTime     int64                // 最近将要超时的时间戳
    // mPit                 map[string]*PITEntry // PIT表
    // timeoutEventHeapLock sync.Locker          // 锁， mpit 、timeoutEventHeap、recentExpireTime
    // timeoutEventHeap     TimeoutEventHeap     // 超时事件堆， 以超时时间排序的最小堆

    // mFib map[string]func(*packet.Interest)

    //
    // @Description: 	通过最长匹配原则查找FIB表，找到合适的兴趣包处理函数，
    // @receiver l
    // @param identifier *component.Identifier
    // @return func(*packet.Interest)  兴趣包处理函数，没有则返回nil
    //
    lookUpFib(identifier) {
        var idComponentN = identifier.getComponents().length
        // console.log("idComponentN:", idComponentN)
        for (var i = idComponentN; i > 0; i--) {
            var prefix
            try {
                prefix = identifier.getPrefix(i)
                // console.log(i, prefix.toUri())
            }catch (err){
                return null
            }
            var fibEntry = this.mFib.get(prefix.toUri())
            if (fibEntry != null) {
                return fibEntry
            }
        }
        return null
    }

    //
    // @Description: 	接收到 MINPacket 后调用 本函数，将 MINPacket 转为 Interest
    // @receiver l
    // @param minPacket *packet.MINPacket
    //
    onReceiveInterest(minPacket) {
        var interest
        try {
            interest = interes.createInterestByMINPacket(minPacket)
        }catch (err){
            console.log(err)
            return
        }
        if (interest.NackHeader.isInitial()) {
            this.onReceiveNack(interest)
            return
        }
        var identifierWraper
        try {
            // Q?
            // 感觉这里实现有问题，应该先填充属性到MINPacket中，否则interest.MINPacket.IdentifierField标识区中不会有数据
            interest.fillDataToFields()
            identifierWraper = interest.MINPacket.IdentifierField.getIdentifier(0)
        }catch (err){
            return
        }
        console.log("identifierWraper.getIdentifier :", identifierWraper.getIdentifier())
        var pitEntry = this.getPitEntryAndDelete(identifierWraper.getIdentifier())
        if (pitEntry === null) {
            console.log("no match pit entry")
            return
        }
        var onInterest = this.lookUpFib(identifierWraper.getIdentifier())
        if (onInterest != null) {
            onInterest(interest)
        }
    }

    //
    // @Description: 	通过名称查询PIT表项，并在PIT中删除该表项
    // @receiver l
    // @param identifier *component.Identifier
    // @return *PITEntry	nil 表示没查到相应表项
    //
    getPitEntryAndDelete(identifier) {
        // 加锁？？
        // this.timeoutEventHeapLock.Lock() // 加锁访问
        // console.log(identifier.toUri(), this.mPit)
        var pitEntry = this.mPit.get(identifier.toUri())
        var ok = this.mPit.has(identifier.toUri())
        if (ok) {
            this.mPit.delete(identifier.toUri())
        } else {
            pitEntry = null
        }
        // this.timeoutEventHeapLock.Unlock() // 解锁
        return pitEntry
    }

    //
    // @Description: 接收到 MINPacket 后调用 本函数，将 MINPacket 转为 Data， 查询PIT表，调用相应的onData回调函数来处理数据包
    // @receiver l
    // @param minPacket *packet.MINPacket
    //
    onReceiveData(minPacket) {
        var datas
        try {
            datas = data.createDataByMinPacket(minPacket)
        }catch (err){
            console.log(err)
            return
        }
        console.log("datas: ", datas.getName())
        var pitEntry = this.getPitEntryAndDelete(datas.getName())
        if (pitEntry == null) {
            console.log("no match pit entry")
            return
        }
        pitEntry.onData(pitEntry.interest, datas)

    }

    //
    // @Description: 	用 onReceiveInterest 调用 ， 如果兴趣包是 NACK，则调用该函数处理NACK包。
    //					用函数查找并删除PIT表项，并使用表项中记录的相应的onNack函数处理NACK包
    // @receiver l
    // @param interest *packet.Interest		兴趣包
    //
    onReceiveNack(interest) {
        var n
        try {
            n = nack.createNackByInterest(interest)
        }catch (err){
            console.log(err)
            return
        }
        // var identifierWraper
        // try {
        //     identifierWraper = interest.getIdentifier(0)
        // }catch (err){
        //     return
        // }
        // var pitEntry = this.getPitEntryAndDelete(interest.getIdentifier())
        var pitEntry = this.getPitEntryAndDelete(interest.getName())
        if (pitEntry === null) {
            console.log("no match pit entry")
            return
        }
        pitEntry.onNack(pitEntry.interest, n)
    }

    //
    // @Description: 	处理超时事件，由 detectTimeout 调用 。 detectTimeout是由 timer定时器根据用户的设置定时触发的。
    //				（1）每次发出一个兴趣包，会产生一个定时事件，放入超时事件堆 timeoutEventHeap
    //				（2）如果超时时事放入timeoutEventHeap后，timeoutEventHeap 数组头的超时事件的超时时间 小于 最近即将发生的超时事
    //				件时间 recentExpireTime
    //					则将 timer 超时时间重新设置为 timeoutEventHeap 数组头的超时事件的超时时间 ，
    //					recentExpireTime 也设置为timeoutEventHeap 数组头的超时事件的超时时间。 这个操作是  tryResetTimer 函数做的
    //				本函数的功能是：
    //				（1）循环判断，如果timeoutEventHeap中还有事件，且 堆顶 的事件超时时间小于当前时间，则将事件从堆中弹出。
    //				（2）通过弹出事件的键值在PIT表中查PIT表项，如果PIT表项存在，再判断PIT表项记录的超时时间是否确实小于等于当前时间 ;
    //				（3）如果PIT表项记录的超时时间确实小于等于当前时间，则调用PIT表项中的超时回调函数onTimeout，
    //				（4）如果PIT表项记录的超时时间大小当前时间，则重新设置timeEvent的超时时间为PIT表项的超时时间，
    //				并把timeEvent重新放回timeoutEventHeap中
    //				（5）循环处理结果后，如果timeoutEventHeap还有元素，则将定时器超时时间和recentExpireTime变量设置成堆顶事件的超时时间
    //				（6）如果timeoutEventHeap没元素了，则将定时器超时时间和recentExpireTime变量设置成一个比较大的值
    // @receiver l
    //
    dealWithTimeout() {
        var currentTime = new Date().getTime()
        // 加锁？
        // this.timeoutEventHeapLock.Lock()
        while (this.timeoutEventHeap.heap.length > 0 && (this.timeoutEventHeap.heap)[0].timeoutTime <= currentTime) {
            var timeEvent = this.timeoutEventHeap.Pop()
            var pitEntry = this.mPit.get(timeEvent.key)
            var ok = this.mPit.has(timeEvent.key)
            if (ok) {
                if (pitEntry.expireTime <= currentTime) {
                    pitEntry.onTimeout(pitEntry.interest)
                    this.mPit.delete(timeEvent.key)
                } else {
                    timeEvent.timeoutTime = pitEntry.expireTime
                    this.timeoutEventHeap.Push(timeEvent)
                }
            }
        }
        if (this.timeoutEventHeap.heap.length > 0) {
            this.timer = setTimeout(this.timer = null, this.timeoutEventHeap.heap[0].timeoutTime - currentTime)
            this.recentExpireTime =(this.timeoutEventHeap.heap)[0].timeoutTime
        } else {
            this.timer = setTimeout(this.timer = null,500000)
            this.recentExpireTime = BigInt(currentTime + 500000)
        }
        // 加锁？？
        // this.timeoutEventHeapLock.Unlock()
    }

    //
    // @Description: 等待 timer超时，触发调用dealWithTimeout函数
    // @receiver l
    //
    detectTimeout() {
        this.dealWithTimeout()
        // while (1){
        //     if (this.timer === null){
        //         break
        //         // this.dealWithTimeout()
        //     }
        // }
        //console.log("1111", this.timer)

    }

    //
    // @Description: 在 processEvent之前先初始化一些东西主要 包括初始化PIT表，和将定时器启动起来
    // @receiver l
    //
    initBeforeProcessEvent() {
        this.mPit = new Map
        this.timer = setTimeout( this.detectTimeout,5000)
        this.recentExpireTime = BigInt(new Date().getTime() + 5000)
        // 开启一个线程？？
        // console.log("超时", this.detectTimeout())

    }

    //
    // @Description: 不断调用ReceivePacket接收包，并按包类型进行区分后，调用相应的函数处理包
    // @receiver l
    //
    doReceivePacket() {
        var minPacket
        while (1) {
            try {
                minPacket = this.ReceivePacket()
            }catch (err){
                // log.Fatal(err)
                console.log(err)
            }
            var identifier
            console.log("minP: ", minPacket)
            try {
                identifier = minPacket.IdentifierField.getIdentifier(0)
            }catch (err){
                console.log("receive error packet", err)
                // log.Println("receive error packet", err)
            }
            // To Do
            console.log("identifier.getIdentifierType: ", identifier.getIdentifierType())
            switch (identifier.getIdentifierType()) {
                case tlv.ComponentType.TlvIdentifierContentInterest:
                    this.onReceiveInterest(minPacket)
                case tlv.ComponentType.TlvIdentifierContentData:
                    this.onReceiveData(minPacket)
            }
        }
    }

    //
    // @Description: 初始化一些变量，然后启动接收包阻塞线程
    // @receiver l
    //
    ProcessEvent() {
        // this.initBeforeProcessEvent()
        for (var i = 0; i < this.pendingPacketList.length; i++) {
            if (this.pendingPacketList[i].data != null) {
                this.PutData(this.pendingPacketList[i].data)
                continue
            }
            this.ExpressInterest(this.pendingPacketList[i].interest, this.pendingPacketList[i].onData,
                this.pendingPacketList[i].onTimeout, this.pendingPacketList[i].onNack)
        }
        this.pendingPacketList = null // release the memory of pendingPacketLt
        this.doReceivePacket()
    }

    //
    // @Description: 尝试重新设置定时器超时时间
    //				（1）每次发出一个兴趣包，会产生一个定时事件，放入超时事件堆 timeoutEventHeap
    //				（2）如果超时时事放入timeoutEventHeap后，timeoutEventHeap 数组头的超时事件的超时时间 小于 最近即将发生的超时事
    //				件时间 recentExpireTime
    //					则将 timer 超时时间重新设置为 timeoutEventHeap 数组头的超时事件的超时时间 ，
    //					recentExpireTime 也设置为timeoutEventHeap 数组头的超时事件的超时时间。
    // @receiver l
    //
    tryResetTimer() {
        // 加锁？？
        // this.timeoutEventHeapLock.Lock()
        if (this.timeoutEventHeap.heap.length <= 0) {
            //加锁？？
            // this.timeoutEventHeapLock.Unlock()
            return
        }
        if (this.timeoutEventHeap.heap[0].timeoutTime < this.recentExpireTime) {
            var durationMs = this.timeoutEventHeap.heap[0].timeoutTime - new Date().getTime()
            this.recentExpireTime = this.timeoutEventHeap.heap[0].timeoutTime
            this.timer = setTimeout(this.timer, durationMs)
        }
        // 加锁？？
        // this.timeoutEventHeapLock.Unlock()
    }

    //
    // @Description: 	将兴趣包放入PIT表中， 并在 timeoutEventHeap 添加超时事件，然后调用 tryResetTimer 尝试重新设置 timer定时器
    // @receiver l
    // @param entry *PITEntry
    // @param interestNameStr string
    // @return bool	如果PIT中已经存在名称相同的兴趣包，则返回 true， 如果不存在返回false
    //
    insert2PIT(entry, interestNameStr) {
        var interestExpireTime = new Date().getTime() + (entry.interest.InterestLifeTime.getInterestLifeTime())
        // 加锁？？
        this.timeoutEventHeapLock.Lock()
        var ok = this.mPit.has(interestNameStr)
        var pitEntryIn = this.mPit.get(interestNameStr)
        if (ok) {
            if (pitEntryIn.expireTime < interestExpireTime) {
                this.mPit.set(interestNameStr, entry)
            }
            // 加锁？？
            this.timeoutEventHeapLock.Unlock()
            return true
        }
        var timeEvent = new timeoutevent.TimeoutEvent
        timeEvent.key = interestNameStr
        timeEvent.timeoutTime = interestExpireTime
        // 加锁？？
        this.timeoutEventHeapLock.Lock()
        this.mPit.set(interestNameStr, entry)
        this.timeoutEventHeap.Push(timeEvent)
        // 加锁？？
        this.timeoutEventHeapLock.Unlock()
        this.tryResetTimer()

        return false
    }

    //
    // @Description: 	发送一个兴趣包
    // @receiver l
    // @param interest *packet.Interest		兴趣包
    // @param onData func(*packet.Interest, *packet.Data)	接收到兴趣包对应的数据包后的处理函数
    // @param onTimeout func(*packet.Interest)	兴趣包超时的回调函数
    // @param onNack func(*packet.Interest, *packet.Nack)		兴趣包无路由的回调函数
    //
    ExpressInterest(interest, onData, onTimeout, onNack) {
        if (this.mPit.size === 0) { // 未启动processEvent
            var pendingPacket = new PendingPacket
            pendingPacket.interest = interest
            pendingPacket.onData = onData
            pendingPacket.onNack = onNack
            pendingPacket.onTimeout = onTimeout
            this.pendingPacketList.push(pendingPacket)
            return
        }

        var interestName
        try {
            interestName = interest.getName()
        }catch (err){
            console.log(err)
            return
        }
        var pitEntry = new PITEntry
        pitEntry.interest = interest
        pitEntry.onData = onData
        pitEntry.onTimeout = onTimeout
        pitEntry.onNack = onNack
        var isRepeat = this.insert2PIT(pitEntry, interestName.toUri())

        if (isRepeat) {
            return
        }

       var err = this.SendInterest(interest)
        if (err != null) {
            console.log(err)
        }
    }

    //
    // @Description: 发出一个数据包
    // @receiver l
    // @param data *packet.Data
    //
    PutData(data) {
        console.log("看看pit", this.mPit.size)
        if (this.mPit.size === 0) { // 未启动processEvent
            console.log(11111111)
            var pendingPacket = new PendingPacket
            pendingPacket.data = data
            this.pendingPacketList.push(pendingPacket)
            return
        }
        console.log(222222222)
        var err = this.SendData(data)
        if (err != null) {
            console.log(err)
            //log.Println(err)
        }
    }

    //
    // @Description: 	设置一个标识监听，并绑定监听的函数，通过fib表来记录什么前缀对应什么兴趣包到达处理函数
    // @receiver l
    // @param identifier *component.Identifier	监听的标识
    // @param onInterest func(*packet.Interest)	收到兴趣包的处理函数
    // @param onRegisterFail func(*component.Identifier)	注册标识失败的回调函数
    // @param onRegisterSuccess func(*component.Identifier)	注册标识成功的回调函数
    //
    RegisterPrefix(identifier, onInterest, onRegisterFail, onRegisterSuccess) {

        if (this.mFib === null) {
            this.mFib = new Map
        }
        var err = this.RegisterIdentifier(identifier, 3)
        if (err != null) {
            onRegisterFail(identifier)
            return
        }
        this.mFib[identifier.toUri()] = onInterest
        onRegisterSuccess(identifier)
    }
}

module.exports = {
    LogicFaceICN: LogicFaceICN,
    PITEntry: PITEntry,
    PendingPacket: PendingPacket
}
